/*
 * Copyright (c) 2013-2018 上海汇数数据. All rights reserved.
 * @(#) Joiners.java 2018-08-06 17:27
 */

package cn.jh.common.core.base;

import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import javax.annotation.Nullable;
import java.util.Map;
import java.util.function.Function;

import static com.google.common.base.Preconditions.checkNotNull;


/**
 * Guava 库中的{@code Joiner}，字符串连接器帮助类和工厂类。
 *
 * @author Yichuan
 * @author Fuchun
 * @version 1.0
 */
public abstract class Joiners {

    private static final Map<String, Joiner> JOINER_MAP = Maps.newHashMap();

    private static Joiner createConstant(String separator, boolean skipNulls, String ... nullText) {
        Joiner joiner = Joiner.on(separator);
        if (skipNulls) {
            joiner = joiner.skipNulls();
        } else {
            if (nullText != null && nullText.length > 0) {
                joiner = joiner.useForNull(nullText[0]);
            }
        }
        String key = getJoinerKey(separator, skipNulls, nullText);
        return addKnownJoiner(key, joiner);
    }

    private static String getJoinerKey(String separator, boolean skipNulls, String... nullText) {
        String key = separator;
        if (skipNulls) {
            key = String.format("%s:skipNulls", separator);
        } else {
            if (nullText != null && nullText.length > 0) {
                key = String.format("%s:Null[%s]", separator, nullText[0]);
            }
        }
        return key;
    }

    private static Joiner addKnownJoiner(String key, Joiner joiner) {
        JOINER_MAP.put(key, joiner);
        return joiner;
    }

    protected static Joiner findOrCreate(String separator, boolean skipNulls, String... nullText) {
        checkNotNull(separator, "separator");
        String key = getJoinerKey(separator, skipNulls, nullText);
        Joiner joiner = JOINER_MAP.get(key);
        if (joiner == null) {
            joiner = createConstant(separator, skipNulls, nullText);
        }
        return joiner;
    }

    /**
     * 使用 ""(空字符串) 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner JOINER = createConstant("", true);

    /**
     * 使用 "@" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner AT_JOINER = createConstant("@", true);

    /**
     * 使用 "," 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner COMMA_JOINER = createConstant(",", true);

    /**
     * 使用 ";" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner SEMICOLON_JOINER = createConstant(";", true);

    /**
     * 使用 "." 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner DOT_JOINER = createConstant(".", true);

    /**
     * 使用 " "(空格) 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner BLANK_JOINER = createConstant(" ", true);

    /**
     * 使用 "?" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner QM_JOINER = createConstant("?", true);

    /**
     * 使用 "&" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner AND_JOINER = createConstant("&", true);

    /**
     * 使用 ":" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner COLON_JOINER = createConstant(":", true);

    /**
     * 使用 "/" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner SLASH_JOINER = createConstant("/", true);

    /**
     * 使用 "\" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner BACKSLASH_JOINER = createConstant("\\", true);

    /**
     * 使用 "\t" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner TAB_JOINER = createConstant("\t", true);

    /**
     * 使用 "\n" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner NL_JOINER = createConstant("\n", true);

    /**
     * 使用 "_" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner UNDERLINE_JOINER = createConstant("_", true);

    /**
     * 使用 "+" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner PLUS_JOINER = createConstant("+", true);

    /**
     * 使用 "-" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner MINUS_JOINER = createConstant("-", true);

    /**
     * 使用 "|" 作为连接符的字符串连接器。该连接器默认忽略{@code null} 值。
     */
    static final Joiner VERTICAL_JOINER = createConstant("|", true);

    /**
     * 使用 "=" 作为键与值的连接符，"&" 作为键值对间的连接符的字符串连接器。
     */
    static final Joiner.MapJoiner QUERY_JOINER = createConstant("&", false, "").withKeyValueSeparator("=");

    /**
     * 使用 "=" 作为键与值的连接符，";" 作为键值对间的连接符的字符串连接器。
     */
    static final Joiner.MapJoiner SEMICOLON_MAP_JOINER = createConstant(";", false, "").withKeyValueSeparator("=");

    /**
     * 使用 ":" 作为键与值的连接符，"," 作为键值对间的连接符的字符串连接器。
     */
    static final Joiner.MapJoiner MAP_JOINER = createConstant(",", false, "").withKeyValueSeparator(":");

    static final Joiner.MapJoiner COMMA_MAP_JOINER = createConstant(",", false, "").withKeyValueSeparator("=");

    static final Function<String, String> URL_ENCODE = UrlKt::encode;

    /**
     * 使用 ""(空字符串) 作为连接符的字符串连接器。该连接器默认忽略 `null` 值。
     */
    public static Joiner empty() {
        return JOINER;
    }

    /**
     * 使用 "@" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner at() {
        return AT_JOINER;
    }

    /**
     * 使用 "," 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner comma() {
        return COMMA_JOINER;
    }

    /**
     * 使用 ";" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner semicolon() {
        return SEMICOLON_JOINER;
    }

    /**
     * 使用 "." 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner dot() {
        return DOT_JOINER;
    }

    /**
     * 使用 " "(空格) 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner blank() {
        return BLANK_JOINER;
    }

    /**
     * 使用 "?" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner qm() {
        return QM_JOINER;
    }

    /**
     * 使用 "&" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner and() {
        return AND_JOINER;
    }

    /**
     * 使用 ":" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner colon() {
        return COLON_JOINER;
    }

    /**
     * 使用 "/" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner slash() {
        return SLASH_JOINER;
    }

    /**
     * 使用 "\\" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner backslash() {
        return BACKSLASH_JOINER;
    }

    /**
     * 使用 "\t" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner tab() {
        return TAB_JOINER;
    }

    /**
     * 使用 "\n" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner nl() {
        return NL_JOINER;
    }

    /**
     * 使用 "_" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner underline() {
        return UNDERLINE_JOINER;
    }

    /**
     * 使用 "+" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner plus() {
        return PLUS_JOINER;
    }

    /**
     * 使用 "-" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner minus() {
        return MINUS_JOINER;
    }

    /**
     * 使用 "|" 作为连接符的字符串连接器。该连接器默认忽略`null` 值。
     */
    public static Joiner vertical() {
        return VERTICAL_JOINER;
    }

    public static Joiner.MapJoiner semicolonMap() {
        return SEMICOLON_MAP_JOINER;
    }

    public static Joiner.MapJoiner commaEqualMap() {
        return COMMA_MAP_JOINER;
    }

    /**
     * 返回指定分隔符{@code separator} 并且忽略{@code null} 值的字符串连接器。
     * <pre>{@code
     * String result = Joiners.skipNull("::").join("a", null, "c", "d");
     * // result = "a::c::d";
     * }</pre>
     *
     * @param separator 分隔符。
     * @return 返回指定分隔符{@code separator} 并且忽略{@code null} 值的字符串连接器。
     */
    @Contract("null -> fail")
    public static Joiner skipNull(String separator) {
        return findOrCreate(separator, true);
    }

    /**
     * 返回指定分隔符{@code separator} 和{@code nullText} 空值替换文本的字符串连接器。
     * <pre>{@code
     * String result = Joiners.useNull("::", "NULL").join("a", null, "c", "d");
     * // result = "a::NULL::c::d";
     * }</pre>
     *
     * @param separator 分隔符。
     * @param nullText 连接的元素是{@code null} 值时的替换文本(not null)。
     * @return 返回指定分隔符{@code separator} 和{@code nullText} 空值替换文本的字符串连接器。
     */
    @Contract("null, _ -> fail; _, null -> fail")
    public static Joiner useNull(String separator, String nullText) {
        checkNotNull(separator, "separator");
        checkNotNull(nullText, "nullText");
        return findOrCreate(separator, false, nullText);
    }

    /**
     * 返回指定分隔符{@code separator} 并且{@code null} 替换为空字符串的字符串连接器。
     * <pre>{@code
     * String result = Joiners.useEmpty("::").join("a", null, "c", "d");
     * // result = "a::::c::d";
     * }</pre>
     *
     * @param separator 分隔符。
     * @return 返回指定分隔符{@code separator} 并且{@code null} 替换为空字符串的字符串连接器。
     */
    public static Joiner useEmpty(String separator) {
        checkNotNull(separator, "separator");
        return findOrCreate(separator, false, "");
    }

    /**
     * 以指定的分隔符{@code separator} 连接指定的集合(数组) 的元素的字符串值，返回新的字符串。
     * <p />
     * 该方法将忽略指定集合中的{@code null} 元素。
     *
     * @param parts 要连接的集合(数组)。
     * @return 返回新的连接字符串。
     */
    @NotNull
    public static String join(Iterable<?> parts) {
        return JOINER.join(parts);
    }

    /**
     * 连接指定的数组的元素的字符串值，返回新的字符串。
     * <p />
     * 该方法将忽略指定数组中的{@code null} 元素。
     *
     * @param parts 要连接的数组。
     * @return 返回新的连接字符串。
     */
    @NotNull
    public static String join(Object[] parts) {
        return JOINER.join(parts);
    }

    /**
     * 连接指定的对象的字符串值，返回新的字符串。。
     * <p />
     * 该方法将忽略指定数组中的{@code null} 元素。
     *
     * @param first 第一个对象。
     * @param second 第二个对象。
     * @param rest 第3..n个对象。
     * @return 返回新的连接的字符串。
     */
    @NotNull
    public static String join(@Nullable Object first, @Nullable Object second, Object... rest) {
        return JOINER.join(first, second, rest);
    }

    @NotNull
    public static String toQueryString(Map<String, Object> params) {
        return toQueryString(params, true);
    }

    @NotNull
    public static String toQueryString(Map<String, Object> params, boolean encode) {
        if (params == null || params.isEmpty()) {
            return "";
        }
        Function<String, String> encodeFunction = encode ? URL_ENCODE : Function.identity();
        Map<String, String> map = Maps.newHashMapWithExpectedSize(params.size());
        params.forEach((k, v) -> {
            if (k != null) {
                map.put(k, v == null ? "" : encodeFunction.apply(v.toString()));
            }
        });
        return QUERY_JOINER.join(map);
    }
}
